VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
  Persistable = 0  'NotPersistable
  DataBindingBehavior = 0  'vbNone
  DataSourceBehavior  = 0  'vbNone
  MTSTransactionMode  = 0  'NotAnMTSObject
END
Attribute VB_Name = "clsFakeBubble"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)

Private Declare Function SelectObject Lib "gdi32.dll" (ByVal hdc As Long, ByVal hObject As Long) As Long
Private Declare Function Ellipse Lib "gdi32.dll" (ByVal hdc As Long, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
Private Declare Function FrameRect Lib "user32.dll" (ByVal hdc As Long, ByRef lpRect As RECT, ByVal hBrush As Long) As Long
Private Declare Function FillRect Lib "user32.dll" (ByVal hdc As Long, ByRef lpRect As RECT, ByVal hBrush As Long) As Long
Private Declare Function MoveToEx Lib "gdi32.dll" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, ByRef lpPoint As Any) As Long
Private Declare Function LineTo Lib "gdi32.dll" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long) As Long
Private Declare Function GetStockObject Lib "gdi32.dll" (ByVal nIndex As Long) As Long
Private Const NULL_PEN As Long = 8
Private Const NULL_BRUSH As Long = 5
Private Const BLACK_BRUSH As Long = 4
Private Declare Function SetPixelV Lib "gdi32.dll" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, ByVal crColor As Long) As Long

Private Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

'width=256
'ball radius=16
'horizontal space=32 vertical space=28
'grid width=8 (7) height=12

'shooter y=362

Private m_bData(63, 63) As Byte

Private m_nWidth As Long
Private m_nHeight As Long 'useless?

Private m_nRadius As Long 'useless?
Private m_nStaticHitTestRadius As Long
Private m_nHitTestRadius As Long

Private m_nHorizontalSpace As Long
Private m_nVerticalSpace As Long

Private m_nGridWidth As Long
Private m_nGridHeight As Long

Private m_nShooterY As Long

Private m_nParity As Long 'indicates whether (0,0) is valid pos

Private m_nNextBubble(1) As Long '0-current 1-next

Private m_nAngle As Double
Private m_nMaxAngle As Double

Private m_nDifficulty As Long
Private m_nMaxShootCount As Long
Private m_nMaxColors As Long
Private m_nMaxTime As Long
Private m_nCurrentTime As Long
Private m_nOldTime As Long

Private m_nScore As Long
Private m_nDeletedCount As Long
Private m_nShootCount As Long
Private m_bGameOver As Boolean

'0-for next bubbles
'1-for random map
'2-for random effects (e.g. chain reaction)
Private m_objRnd(2) As New clsMT19937

'ray cast data
Private Type typeRaycastData
 x As Double
 y As Double
 dist As Double
End Type

Private m_nRaycastA As Double
Private m_nRaycastB As Double
Private m_tRaycast(255) As typeRaycastData
Private m_nRaycastCount As Long

Private m_nRaycastHitX As Long
Private m_nRaycastHitY As Long
Private m_nRaycastDestX As Long
Private m_nRaycastDestY As Long

Private m_nRaycastAnimation As Long

'flying bubble
Private m_nFlyingBubble As Long
Private m_nFlyingIndex As Long
Private m_nFlyingDist As Double

Private Type typeFallingBubble
 x As Single
 y As Single
 vx As Single
 vy As Single
 nColor As Long
 nTargetX As Long
 nTargetY As Long
 nTargetTime As Long
End Type

Private m_tFallingBubble() As typeFallingBubble
Private m_nFallingBubbleCount As Long
Private m_nFallingBubbleMax As Long

'chain reaction
Private m_bChainReaction As Boolean
Private m_nChainReactionLevel As Long
Private m_nChainReactionLevel2 As Long

'obstacles
Private Type typeObstacle
 x As Long
 y As Long
 nColor As Long
End Type

Private m_nPendingObstacles As Long
Private m_nObstacleTime As Long
Private m_nObstacleCount As Long
Private m_tObstacles(63) As typeObstacle

'bomb animation
Private m_nBombAnimation As Long
Private m_bBomb(63, 63) As Byte

Friend Property Get Difficulty() As Long
Difficulty = m_nDifficulty
End Property

Friend Property Let Difficulty(ByVal n As Long)
If n < 0 Then n = 0 Else If n > 3 Then n = 3
m_nDifficulty = n
m_nMaxShootCount = 12 - n * 2&
m_nMaxTime = m_nMaxShootCount * 60&
m_nCurrentTime = m_nMaxTime
m_nMaxColors = 6 + n
If m_nMaxColors > 8 Then m_nMaxColors = 8
End Property

Friend Property Get IsGameOver() As Boolean
IsGameOver = m_bGameOver
End Property

Friend Property Get Score() As Long
Score = m_nScore
End Property

Friend Property Get Angle() As Double
Angle = m_nAngle
End Property

Friend Property Let Angle(ByVal f As Double)
If f > m_nMaxAngle Then f = m_nMaxAngle _
Else If f < -m_nMaxAngle Then f = -m_nMaxAngle
m_nAngle = f
End Property

Friend Property Get DeletedCount() As Long
DeletedCount = m_nDeletedCount
End Property

Friend Property Let DeletedCount(ByVal n As Long)
m_nDeletedCount = n
End Property

Friend Sub AddObstacles(ByVal nCount As Long)
m_nPendingObstacles = m_nPendingObstacles + nCount
End Sub

'warning: no sanity check!!!
Friend Sub Create(Optional ByVal nWidth As Long = 256, Optional ByVal nHeight As Long = 480, _
Optional ByVal nRadius As Long = 16, Optional ByVal nStaticHitTestRadius As Long, Optional ByVal nHitTestRadius As Long, _
Optional ByVal nHorizontalSpace As Long, Optional ByVal nVerticalSpace As Long, _
Optional ByVal nGridWidth As Long, Optional ByVal nGridHeight As Long = 12, Optional ByVal nShooterY As Long = 362, Optional ByVal nDifficulty As Long = -1)
'///
If nStaticHitTestRadius <= 0 Then nStaticHitTestRadius = nRadius
If nHitTestRadius <= 0 Then nHitTestRadius = nRadius
If nHorizontalSpace <= 0 Then nHorizontalSpace = nRadius
If nVerticalSpace <= 0 Then nVerticalSpace = nHorizontalSpace * Sqr(3)
If nGridWidth <= 0 Then nGridWidth = nWidth \ nHorizontalSpace - 1
If nGridWidth < 2 Then nGridWidth = 2 Else If nGridWidth > 64 Then nGridWidth = 64
If nGridHeight > 63 Then nGridHeight = 63
If nDifficulty < 0 Then nDifficulty = m_nDifficulty
'///
Erase m_bData
m_nWidth = nWidth
m_nHeight = nHeight
m_nRadius = nRadius
m_nStaticHitTestRadius = nStaticHitTestRadius
m_nHitTestRadius = nHitTestRadius
m_nHorizontalSpace = nHorizontalSpace
m_nVerticalSpace = nVerticalSpace
m_nGridWidth = nGridWidth
m_nGridHeight = nGridHeight
m_nShooterY = nShooterY
m_nParity = 0
m_nAngle = 0
m_nRaycastCount = 0
m_nRaycastHitX = -1
m_nRaycastHitY = -1
m_nRaycastDestX = -1
m_nRaycastDestY = -1
m_nRaycastAnimation = 0
m_nScore = 0
m_nShootCount = 0
m_nDeletedCount = 0
m_bGameOver = False
m_nFlyingBubble = 0
m_nFallingBubbleCount = 0
Difficulty = nDifficulty
m_bChainReaction = False
m_nPendingObstacles = 0
m_nObstacleTime = 0
m_nObstacleCount = 0
m_nOldTime = 0
m_nBombAnimation = 0
'///FIXME:
m_nMaxAngle = Atn(m_nGridWidth)
'///
End Sub

Friend Sub InitRNG(ByRef objRnd() As clsMT19937)
Dim i As Long
For i = 0 To 2
 m_objRnd(i).mt_random_copy objRnd(i)
Next i
End Sub

Friend Sub InitNextBubbles()
GenerateNextBubble
GenerateNextBubble
End Sub

Friend Sub GenerateNextBubble()
Dim i As Long
Select Case m_objRnd(0).mt_random_real
Case Is < 0.02
 i = &H101& 'bomb
Case Is < 0.04
 i = &HFF& 'any color
Case Is < 0.06 '+2
 i = &H11 + Int(m_objRnd(0).mt_random_real * m_nMaxColors)
Case Is < 0.08 'colored bomb
 i = &H21 + Int(m_objRnd(0).mt_random_real * m_nMaxColors)
Case Else
 i = 1 + Int(m_objRnd(0).mt_random_real * m_nMaxColors)
End Select
'///
m_nNextBubble(0) = m_nNextBubble(1)
m_nNextBubble(1) = i
End Sub

Friend Function GetRandomMapBubble(Optional ByVal nIndex As Long = 1) As Long
Dim i As Long
Select Case m_objRnd(nIndex).mt_random_real
Case Is < 0.02
 i = &HFF& 'any color
Case Is < 0.04 '+2
 i = &H11 + Int(m_objRnd(nIndex).mt_random_real * m_nMaxColors)
Case Is < 0.06 'colored bomb
 i = &H21 + Int(m_objRnd(nIndex).mt_random_real * m_nMaxColors)
Case Else
 i = 1 + Int(m_objRnd(nIndex).mt_random_real * m_nMaxColors)
End Select
GetRandomMapBubble = i
End Function

Friend Property Get Parity() As Long
Parity = m_nParity
End Property

'warning: no sanity check!!!
Friend Property Let Parity(ByVal n As Long)
m_nParity = n And 1&
End Property

Friend Sub InitRandomMap(Optional ByVal nCount As Long = 5)
Dim i As Long, j As Long
If nCount > m_nGridHeight - 1 Then nCount = m_nGridHeight - 1
Erase m_bData
For j = nCount - 1 To 0 Step -1
 For i = 0 To m_nGridWidth - 1
  If ((i Xor j Xor m_nParity) And 1&) = 0 Then
   m_bData(i, j) = GetRandomMapBubble
  End If
 Next i
Next j
End Sub

Friend Sub GenerateNextRow(Optional ByVal nCount As Long = 1)
Dim i As Long, j As Long
If nCount > m_nGridHeight - 1 Then nCount = m_nGridHeight - 1
CopyMemory m_bData(0, nCount), m_bData(0, 0), (m_nGridHeight + 1 - nCount) * 64&
m_nParity = (m_nParity Xor nCount) And 1&
For j = nCount - 1 To 0 Step -1
 For i = 0 To m_nGridWidth - 1
  If ((i Xor j Xor m_nParity) And 1&) = 0 Then
   m_bData(i, j) = GetRandomMapBubble
  Else
   m_bData(i, j) = 0
  End If
 Next i
Next j
End Sub

Friend Property Get LevelData(ByVal x As Long, ByVal y As Long) As Long
LevelData = m_bData(x, y)
End Property

Friend Property Let LevelData(ByVal x As Long, ByVal y As Long, ByVal d As Long)
Debug.Assert ((x Xor y Xor m_nParity) And 1&) = 0 Or d = 0
m_bData(x, y) = d
End Property

Friend Sub EraseBackground(ByVal hdc As Long, ByVal nLeft As Long, ByVal nTop As Long)
Dim r As RECT
'///background
r.Left = nLeft
r.Top = nTop
r.Right = r.Left + m_nWidth
r.Bottom = r.Top + m_nHeight
FillRect hdc, r, GetStockObject(BLACK_BRUSH)
End Sub

'internal function
Friend Sub DrawBubble(ByVal hdc As Long, ByVal d As Long, ByRef r As RECT, ByRef Brushes() As Long, ByRef Pens() As Long, ByVal objFont As CLogFont, ByVal objFont2 As CLogFont, ByVal bDrawNumber As Boolean)
Select Case d
Case 0 'none
Case 1 To 8 'normal
 SelectObject hdc, Brushes(d)
 Ellipse hdc, r.Left, r.Top, r.Right, r.Bottom
 If bDrawNumber Then
  objFont2.DrawTextXP hdc, CStr(d), r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top, DT_CENTER Or DT_VCENTER Or DT_SINGLELINE, vbBlack, , True
 End If
Case &H11 To &H18 '+2
 SelectObject hdc, Brushes(d And &HF&)
 Ellipse hdc, r.Left, r.Top, r.Right, r.Bottom
 objFont.DrawTextXP hdc, "+2", r.Left + 1, r.Top + 1, r.Right - r.Left, r.Bottom - r.Top, DT_CENTER Or DT_VCENTER Or DT_SINGLELINE, vbWhite, , True
 objFont.DrawTextXP hdc, "+2", r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top, DT_CENTER Or DT_VCENTER Or DT_SINGLELINE, vbBlack, , True
Case &H21 To &H28 'colored bomb
 SelectObject hdc, Brushes(d And &HF&)
 Ellipse hdc, r.Left, r.Top, r.Right, r.Bottom
 objFont2.DrawTextXP hdc, "B", r.Left + 1, r.Top + 1, r.Right - r.Left, r.Bottom - r.Top, DT_CENTER Or DT_VCENTER Or DT_SINGLELINE Or DT_NOCLIP, vbWhite, , True
 objFont2.DrawTextXP hdc, "B", r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top, DT_CENTER Or DT_VCENTER Or DT_SINGLELINE Or DT_NOCLIP, vbBlack, , True
Case &HFF& 'any color
 SelectObject hdc, Pens(2)
 SelectObject hdc, GetStockObject(BLACK_BRUSH)
 Ellipse hdc, r.Left, r.Top, r.Right, r.Bottom
 objFont2.DrawTextXP hdc, ChrW(&H2200), r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top, DT_CENTER Or DT_VCENTER Or DT_SINGLELINE Or DT_NOCLIP, vbWhite, , True
 SelectObject hdc, GetStockObject(NULL_PEN)
Case &H101& 'bomb
 SelectObject hdc, Pens(2)
 SelectObject hdc, GetStockObject(BLACK_BRUSH)
 Ellipse hdc, r.Left, r.Top, r.Right, r.Bottom
 objFont2.DrawTextXP hdc, "B", r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top, DT_CENTER Or DT_VCENTER Or DT_SINGLELINE Or DT_NOCLIP, vbWhite, , True
 SelectObject hdc, GetStockObject(NULL_PEN)
Case Else
 Debug.Assert False
End Select
End Sub

Friend Sub Draw(ByVal hdc As Long, ByVal nLeft As Long, ByVal nTop As Long, ByRef Brushes() As Long, ByRef Pens() As Long, ByVal objFont As CLogFont, ByVal objFont2 As CLogFont, ByVal bLaserSight As Boolean, ByVal bDrawNumber As Boolean)
Dim r As RECT
Dim i As Long, j As Long, k As Long
Dim d As Long
Dim x As Single, y As Single
Dim a As Single, b As Single
Dim tRaycast As typeRaycastData
'///lower bound
k = m_nRadius * 2
r.Left = nLeft
r.Top = nTop + (m_nGridHeight - 1) * m_nVerticalSpace + k
r.Right = r.Left + m_nWidth
r.Bottom = r.Top + 1
FillRect hdc, r, Brushes(1)
'///bubbles
SelectObject hdc, GetStockObject(NULL_PEN)
r.Top = nTop
For j = 0 To m_nGridHeight
 r.Bottom = r.Top + k
 r.Left = nLeft
 For i = 0 To m_nGridWidth - 1
  r.Right = r.Left + k
  d = m_bData(i, j)
  Debug.Assert ((i Xor j Xor m_nParity) And 1&) = 0 Or d = 0
  DrawBubble hdc, d, r, Brushes, Pens, objFont, objFont2, bDrawNumber
  r.Left = r.Left + m_nHorizontalSpace
 Next i
 r.Top = r.Top + m_nVerticalSpace
Next j
'///next bubbles
r.Left = nLeft + m_nWidth \ 2 - m_nRadius
r.Top = nTop + m_nShooterY - m_nRadius
r.Right = r.Left + k
r.Bottom = r.Top + k
For i = 0 To 1
 DrawBubble hdc, m_nNextBubble(i), r, Brushes, Pens, objFont, objFont2, bDrawNumber
 r.Left = r.Right
 r.Right = r.Left + k
Next i
'///flying bubbles
If m_nFlyingBubble <> 0 Then
 tRaycast = m_tRaycast(m_nFlyingIndex)
 x = nLeft + tRaycast.x + m_nFlyingDist * m_nRaycastA * (1& - (m_nFlyingIndex And 1&) * 2&)
 y = nTop + tRaycast.y + m_nFlyingDist * m_nRaycastB
 r.Left = x - m_nRadius
 r.Top = y - m_nRadius
 r.Right = x + m_nRadius
 r.Bottom = y + m_nRadius
 DrawBubble hdc, m_nFlyingBubble, r, Brushes, Pens, objFont, objFont2, bDrawNumber
End If
'///falling bubbles
SelectObject hdc, GetStockObject(NULL_PEN)
For i = 0 To m_nFallingBubbleCount - 1
 x = m_tFallingBubble(i).x
 y = m_tFallingBubble(i).y
 '///chain reaction?
 If m_tFallingBubble(i).nTargetTime > 0 Then
  a = m_tFallingBubble(i).nTargetTime / 64!
  a = a * a
  x = x + (m_tFallingBubble(i).nTargetX * m_nHorizontalSpace + m_nRadius - x) * a
  y = y + (m_tFallingBubble(i).nTargetY * m_nVerticalSpace + m_nRadius - y) * a
 End If
 '///
 x = x + nLeft
 y = y + nTop
 r.Left = x - m_nRadius
 r.Top = y - m_nRadius
 r.Right = x + m_nRadius
 r.Bottom = y + m_nRadius
 DrawBubble hdc, m_tFallingBubble(i).nColor, r, Brushes, Pens, objFont, objFont2, bDrawNumber
Next i
'///obstacles
If m_nObstacleTime > 0 Then
 For i = 0 To m_nObstacleCount - 1
  r.Left = nLeft + m_tObstacles(i).x * m_nHorizontalSpace
  r.Top = nTop + m_tObstacles(i).y * m_nVerticalSpace + m_nObstacleTime * 32&
  r.Right = r.Left + k
  r.Bottom = r.Top + k
  DrawBubble hdc, m_tObstacles(i).nColor, r, Brushes, Pens, objFont, objFont2, bDrawNumber
 Next i
End If
'///bomb animation
If m_nBombAnimation > 0 Then
 SelectObject hdc, Pens(5)
 SelectObject hdc, GetStockObject(NULL_BRUSH)
 r.Top = nTop + m_nRadius
 For j = 0 To m_nGridHeight
  r.Left = nLeft + m_nRadius
  For i = 0 To m_nGridWidth - 1
   If m_bBomb(i, j) Then
    For r.Right = m_nBombAnimation - 3 To m_nBombAnimation
     If r.Right > 9 Then Exit For
     If r.Right >= 1 Then
      r.Bottom = (r.Right * m_nRadius) \ 3&
      Ellipse hdc, r.Left - r.Bottom, r.Top - r.Bottom, r.Left + r.Bottom, r.Top + r.Bottom
     End If
    Next r.Right
   End If
   r.Left = r.Left + m_nHorizontalSpace
  Next i
  r.Top = r.Top + m_nVerticalSpace
 Next j
 m_nBombAnimation = m_nBombAnimation + 1
 If m_nBombAnimation >= 13 Then m_nBombAnimation = 0
End If
'///arrows
SelectObject hdc, Pens(2)
SelectObject hdc, GetStockObject(NULL_BRUSH)
r.Left = nLeft + m_nWidth \ 2
r.Top = nTop + m_nShooterY
Ellipse hdc, r.Left - 48, r.Top - 48, r.Left + 48, r.Top + 48
For i = 0 To 15
 y = m_nAngle + i * 0.3926991!
 x = Cos(y)
 y = Sin(y)
 r.Right = r.Left + y * 48
 r.Bottom = r.Top - x * 48
 MoveToEx hdc, r.Right, r.Bottom, ByVal 0
 If i = 0 Then
  LineTo hdc, r.Left, r.Top
  MoveToEx hdc, r.Right, r.Bottom, ByVal 0
  LineTo hdc, r.Left + y * 40 + x * 4, r.Top - x * 40 + y * 4
  MoveToEx hdc, r.Right, r.Bottom, ByVal 0
  LineTo hdc, r.Left + y * 40 - x * 4, r.Top - x * 40 - y * 4
 ElseIf i And 3& Then
  LineTo hdc, r.Left + y * 42, r.Top - x * 42
 Else
  LineTo hdc, r.Left + y * 36, r.Top - x * 36
 End If
Next i
'///========TEST ONLY: raycast lines
If Not m_bGameOver And m_nFlyingBubble = 0 And bLaserSight Then
 DoRaycast 'slow...
 If m_nRaycastCount > 0 Then
  m_nRaycastAnimation = (m_nRaycastAnimation + 1) And 15&
  j = m_nRaycastAnimation
  a = m_nRaycastA
  b = m_nRaycastB
  For i = 0 To m_nRaycastCount - 1
   tRaycast = m_tRaycast(i)
   k = tRaycast.dist
   x = nLeft + tRaycast.x
   y = nTop + tRaycast.y
   Do While j < k
    SetPixelV hdc, x + a * j, y + b * j, vbWhite
    j = j + 16
   Loop
   If i = m_nRaycastCount - 1 Then
    x = x + a * tRaycast.dist
    y = y + b * tRaycast.dist
    Ellipse hdc, x - m_nRadius, y - m_nRadius, x + m_nRadius, y + m_nRadius
   End If
   j = j - k
   a = -a
  Next i
  '///
  x = nLeft + m_nRaycastDestX * m_nHorizontalSpace + m_nRadius
  y = nTop + m_nRaycastDestY * m_nVerticalSpace + m_nRadius
  SelectObject hdc, Pens(1)
  Ellipse hdc, x - m_nRadius, y - m_nRadius, x + m_nRadius, y + m_nRadius
  If m_nRaycastHitX >= 0 And m_nRaycastHitY >= 0 Then
   x = nLeft + m_nRaycastHitX * m_nHorizontalSpace + m_nRadius
   y = nTop + m_nRaycastHitY * m_nVerticalSpace + m_nRadius
   SelectObject hdc, Pens(3)
   Ellipse hdc, x - 4, y - 4, x + 4, y + 4
   SelectObject hdc, Pens(4)
   Ellipse hdc, x - 6, y - 6, x + 6, y + 6
  End If
 End If
End If
'///========
'///statistics
If m_nPendingObstacles > 0 Then
 SelectObject hdc, Pens(2)
 SelectObject hdc, Brushes(1)
 r.Left = nLeft + 4
 r.Top = nTop + m_nHeight - 60
 For i = 1 To m_nPendingObstacles
  If i > 16 Then Exit For
  Ellipse hdc, r.Left, r.Top, r.Left + 8, r.Top + 8
  r.Left = r.Left + 10
 Next i
 If m_nPendingObstacles > 16 Then
  objFont.DrawTextXP hdc, CStr(m_nPendingObstacles), nLeft + 170, nTop + m_nHeight - 64, 64, 16, DT_VCENTER Or DT_SINGLELINE, vbWhite, , True
 End If
End If
'///
objFont.DrawTextXP hdc, "Time", nLeft + 8, nTop + m_nHeight - 48, 64, 16, DT_VCENTER Or DT_SINGLELINE, vbWhite, , True
objFont.DrawTextXP hdc, "Progress", nLeft + 8, nTop + m_nHeight - 32, 64, 16, DT_VCENTER Or DT_SINGLELINE, vbWhite, , True
objFont.DrawTextXP hdc, "Score", nLeft + 8, nTop + m_nHeight - 16, 64, 16, DT_VCENTER Or DT_SINGLELINE, vbWhite, , True
objFont.DrawTextXP hdc, Format(m_nScore, "#,0"), nLeft + 64, nTop + m_nHeight - 16, 128, 16, DT_VCENTER Or DT_SINGLELINE, vbWhite, , True
'///
r.Left = nLeft + 64
r.Top = nTop + m_nHeight - 46
r.Right = r.Left + 128
r.Bottom = r.Top + 12
FrameRect hdc, r, Brushes(2)
r.Right = r.Left + (m_nCurrentTime * 128) \ m_nMaxTime
FillRect hdc, r, Brushes(2)
'///
r.Top = r.Top + 16
r.Right = r.Left + 128
r.Bottom = r.Top + 12
FrameRect hdc, r, Brushes(2)
r.Right = r.Left + (m_nShootCount * 128) \ (m_nMaxShootCount - 1)
FillRect hdc, r, Brushes(2)
'///
End Sub

'///line equation is x-x0=t*a, y-y0=t*b, a^2+b^2 should be 1, b should <0
'///only bubbles above y0 are checked
'///return value: 0=hit border, 1=hit some bubble with additional return values
Friend Function RaycastInternal(ByVal x0 As Double, ByVal y0 As Double, ByVal a As Double, ByVal b As Double, ByRef dist As Double, ByRef xHit As Long, ByRef yHit As Long, ByRef xx As Long, ByRef yy As Long) As Long
Dim i As Long, j As Long
Dim m1 As Long, m2 As Long
Dim n1 As Long, n2 As Long
Dim x As Double, y As Double
Dim r As Double, r2 As Double
Dim f As Double
'///
xHit = -1
yHit = -1
xx = -1
yy = -1
dist = 1E+300
'///
Debug.Assert a > -1 And a < 1 And b < 0
'///
m1 = Int((y0 + m_nRadius) / m_nVerticalSpace)
If m1 > m_nGridHeight Then m1 = m_nGridHeight
r = m_nStaticHitTestRadius + m_nHitTestRadius
r2 = r * r
'///check if we hit something
For j = m1 To 0 Step -1
 y = j * m_nVerticalSpace + m_nRadius - y0
 x = x0 + y / b * a
 '///check top row (j=0)
 If j = 0 Then
  If x >= m_nHitTestRadius And x <= m_nWidth - m_nHitTestRadius Then
   f = y / b
   If f < dist Then
    dist = f
    xHit = -1
    yHit = -1
   End If
  End If
 End If
 '///
 n1 = Int((x + r / b - m_nRadius) / m_nHorizontalSpace)
 n2 = Int((x - r / b + m_nRadius) / m_nHorizontalSpace)
 If (n2 < 0 Or n1 >= m_nGridWidth) And j < m1 Then Exit For
 If n1 < 0 Then n1 = 0
 If n2 >= m_nGridWidth Then n2 = m_nGridWidth - 1
 '///
 For i = n1 To n2
  If ((i Xor j Xor m_nParity) And 1&) = 0 Then
   If m_bData(i, j) Then
    x = i * m_nHorizontalSpace + m_nRadius - x0
    f = x * b - y * a
    f = r * r - f * f
    If f > 0 Then
     f = x * a + y * b - Sqr(f)
     'Debug.Assert f >= 0 'test only, maybe it can be <0
     If f < dist Then
      dist = f
      xHit = i
      yHit = j
     End If
    End If
   End If
  Else
   Debug.Assert m_bData(i, j) = 0
  End If
 Next i
Next j
'///now find nearest available empty position
If dist < 1E+30 Then
 x = x0 + dist * a
 y = y0 + dist * b
 If x >= m_nHitTestRadius And x <= m_nWidth - m_nHitTestRadius Then
  m1 = (y - m_nRadius) / m_nVerticalSpace
  n1 = (x - m_nRadius) / m_nHorizontalSpace
  m2 = m1 + 1
  m1 = m1 - 1
  n2 = n1 + 2
  n1 = n1 - 2
  If m1 < 0 Then m1 = 0 Else If m1 > m_nGridHeight Then m1 = m_nGridHeight
  If m2 < 0 Then m2 = 0 Else If m2 > m_nGridHeight Then m2 = m_nGridHeight
  If n1 < 0 Then n1 = 0 Else If n1 >= m_nGridWidth Then n1 = m_nGridWidth - 1
  If n2 < 0 Then n2 = 0 Else If n2 >= m_nGridWidth Then n2 = m_nGridWidth - 1
  f = 1E+30
  For j = m1 To m2
   For i = n1 To n2
    If ((i Xor j Xor m_nParity) And 1&) = 0 Then
     If m_bData(i, j) = 0 Then
      r = j * m_nVerticalSpace + m_nRadius - y
      r2 = i * m_nHorizontalSpace + m_nRadius - x
      r = r * r + r2 * r2
      If r < f Then
       f = r
       xx = i
       yy = j
      End If
     End If
    End If
   Next i
  Next j
  Debug.Assert xx >= 0 And yy >= 0
  RaycastInternal = 1
 End If
End If
End Function

Friend Sub DoRaycast()
Dim x As Double, y As Double
Dim a As Double, b As Double
Dim t As typeRaycastData
Dim i As Long
'///
If m_bGameOver Or m_nFlyingBubble <> 0 Then Exit Sub
'///
t.x = m_nWidth \ 2
t.y = m_nShooterY
a = Sin(m_nAngle)
b = -Cos(m_nAngle)
m_nRaycastA = a
m_nRaycastB = b
m_nRaycastCount = 0
'///
Do
 i = RaycastInternal(t.x, t.y, a, b, t.dist, m_nRaycastHitX, m_nRaycastHitY, m_nRaycastDestX, m_nRaycastDestY)
 Select Case i
 Case 0
  Debug.Assert Asc(a / b) > m_nWidth / m_nHeight
  If a > 0 Then x = m_nWidth - m_nHitTestRadius _
  Else x = m_nHitTestRadius
  y = t.y + b / a * (x - t.x)
  Debug.Assert y > 0 And y < t.y
  '///
  t.dist = (x - t.x) / a
  m_tRaycast(m_nRaycastCount) = t
  m_nRaycastCount = m_nRaycastCount + 1
  '///
  t.x = x
  t.y = y
  a = -a
 Case 1
  m_tRaycast(m_nRaycastCount) = t
  m_nRaycastCount = m_nRaycastCount + 1
  Exit Do
 Case Else
  Debug.Assert False
  Exit Do
 End Select
Loop
End Sub

Friend Sub OnShoot()
'///
If m_bGameOver Or m_nFlyingBubble <> 0 Or m_bChainReaction Or m_nObstacleTime > 0 Then Exit Sub
'///
If m_nRaycastCount <= 0 Then DoRaycast
If m_nRaycastCount <= 0 Or m_nRaycastDestX < 0 Or m_nRaycastDestY < 0 Then Exit Sub
'///
m_nOldTime = m_nCurrentTime
m_nCurrentTime = m_nMaxTime
m_nChainReactionLevel = 1
m_nChainReactionLevel2 = 0
m_nFlyingBubble = m_nNextBubble(0)
m_nFlyingIndex = 0
m_nFlyingDist = 0
'///
GenerateNextBubble
End Sub

'internal function
Friend Function EliminateColorCheck(ByVal nColor1 As Long, ByVal nColor2 As Long) As Boolean
EliminateColorCheck = nColor1 = &HFF& Or (nColor1 And &HF&) = (nColor2 And &HF&)
End Function

Friend Sub EliminateBubble(Optional ByVal x0 As Long = -1, Optional ByVal y0 As Long = -1, Optional ByVal nColor As Long = -1)
Dim tmp(63, 63) As Byte '1=eliminated 2=disconnected
Dim nStackX() As Byte
Dim nStackY() As Byte
Dim nStackSize As Long, nStackMax As Long
Dim x As Long, y As Long
Dim i As Long, j As Long, k As Long
Dim ii As Long, jj As Long, kk As Long
Dim iii As Long, jjj As Long
Dim tmp2(11) As Long, tmp2Count As Long
Dim nCount As Long
Dim nScoreBase As Long
Dim t As typeFallingBubble
Dim fx As Single, fy As Single, f As Single
'///
m_bChainReaction = False
If m_bGameOver Then Exit Sub
If x0 < 0 Or y0 < 0 Or nColor <= 0 Then
 If m_nRaycastCount <= 0 Then Exit Sub
 x0 = m_nRaycastDestX
 y0 = m_nRaycastDestY
 nColor = m_nFlyingBubble
Else
 'assume it comes from chain reaction
 m_nRaycastCount = 0
 m_nRaycastDestX = -1
 m_nRaycastDestY = -1
 m_nChainReactionLevel = m_nChainReactionLevel * 2
End If
If x0 < 0 Or y0 < 0 Or nColor <= 0 Then Exit Sub
'///
If nColor <= &HFF& Then m_bData(x0, y0) = nColor
i = m_nOldTime \ (4 - m_nDifficulty)
nScoreBase = 5000 * (m_nDifficulty + 1) + y0 * 10 + i
If m_nRaycastCount <= 1 Then m_nScore = m_nScore + (y0 + 1) * 10 + i
nScoreBase = nScoreBase * m_nChainReactionLevel
'///
Erase m_bBomb
'///========
nStackMax = 128
ReDim nStackX(127), nStackY(127)
'///
Select Case nColor
Case &H101& 'bomb
 nCount = 1
 '///
 x = x0 - 1: y = y0 - 1
 If x >= 0 And y >= 0 Then
  If m_bData(x, y) Then tmp(x, y) = 1: nCount = nCount + 1
 End If
 '///
 x = x0 + 1: y = y0 - 1
 If x < m_nGridWidth And y >= 0 Then
  If m_bData(x, y) Then tmp(x, y) = 1: nCount = nCount + 1
 End If
 '///
 x = x0 - 2: y = y0
 If x >= 0 Then
  If m_bData(x, y) Then tmp(x, y) = 1: nCount = nCount + 1
 End If
 '///
 x = x0 + 2: y = y0
 If x < m_nGridWidth Then
  If m_bData(x, y) Then tmp(x, y) = 1: nCount = nCount + 1
 End If
 '///
 x = x0 - 1: y = y0 + 1
 If x >= 0 And y <= m_nGridHeight Then
  If m_bData(x, y) Then tmp(x, y) = 1: nCount = nCount + 1
 End If
 '///
 x = x0 + 1: y = y0 + 1
 If x < m_nGridWidth And y <= m_nGridHeight Then
  If m_bData(x, y) Then tmp(x, y) = 1: nCount = nCount + 1
 End If
 '///animation
 m_nBombAnimation = 1
 m_bBomb(x0, y0) = 1
Case Else
 '///do flood fill
 nStackX(0) = x0
 nStackY(0) = y0
 tmp(x0, y0) = 1
 nStackSize = 1
 nCount = 1
 Do While nStackSize > 0
  nStackSize = nStackSize - 1
  x0 = nStackX(nStackSize)
  y0 = nStackY(nStackSize)
  '///
  x = x0 - 1: y = y0 - 1
  If x >= 0 And y >= 0 Then
   If EliminateColorCheck(m_bData(x, y), nColor) And tmp(x, y) = 0 Then
    tmp(x, y) = 1
    If nStackSize >= nStackMax Then nStackMax = nStackMax + 128: ReDim Preserve nStackX(nStackMax - 1), nStackY(nStackMax - 1)
    nStackX(nStackSize) = x
    nStackY(nStackSize) = y
    nStackSize = nStackSize + 1
    nCount = nCount + 1
   End If
  End If
  '///
  x = x0 + 1: y = y0 - 1
  If x < m_nGridWidth And y >= 0 Then
   If EliminateColorCheck(m_bData(x, y), nColor) And tmp(x, y) = 0 Then
    tmp(x, y) = 1
    If nStackSize >= nStackMax Then nStackMax = nStackMax + 128: ReDim Preserve nStackX(nStackMax - 1), nStackY(nStackMax - 1)
    nStackX(nStackSize) = x
    nStackY(nStackSize) = y
    nStackSize = nStackSize + 1
    nCount = nCount + 1
   End If
  End If
  '///
  x = x0 - 2: y = y0
  If x >= 0 Then
   If EliminateColorCheck(m_bData(x, y), nColor) And tmp(x, y) = 0 Then
    tmp(x, y) = 1
    If nStackSize >= nStackMax Then nStackMax = nStackMax + 128: ReDim Preserve nStackX(nStackMax - 1), nStackY(nStackMax - 1)
    nStackX(nStackSize) = x
    nStackY(nStackSize) = y
    nStackSize = nStackSize + 1
    nCount = nCount + 1
   End If
  End If
  '///
  x = x0 + 2: y = y0
  If x < m_nGridWidth Then
   If EliminateColorCheck(m_bData(x, y), nColor) And tmp(x, y) = 0 Then
    tmp(x, y) = 1
    If nStackSize >= nStackMax Then nStackMax = nStackMax + 128: ReDim Preserve nStackX(nStackMax - 1), nStackY(nStackMax - 1)
    nStackX(nStackSize) = x
    nStackY(nStackSize) = y
    nStackSize = nStackSize + 1
    nCount = nCount + 1
   End If
  End If
  '///
  x = x0 - 1: y = y0 + 1
  If x >= 0 And y <= m_nGridHeight Then
   If EliminateColorCheck(m_bData(x, y), nColor) And tmp(x, y) = 0 Then
    tmp(x, y) = 1
    If nStackSize >= nStackMax Then nStackMax = nStackMax + 128: ReDim Preserve nStackX(nStackMax - 1), nStackY(nStackMax - 1)
    nStackX(nStackSize) = x
    nStackY(nStackSize) = y
    nStackSize = nStackSize + 1
    nCount = nCount + 1
   End If
  End If
  '///
  x = x0 + 1: y = y0 + 1
  If x < m_nGridWidth And y <= m_nGridHeight Then
   If EliminateColorCheck(m_bData(x, y), nColor) And tmp(x, y) = 0 Then
    tmp(x, y) = 1
    If nStackSize >= nStackMax Then nStackMax = nStackMax + 128: ReDim Preserve nStackX(nStackMax - 1), nStackY(nStackMax - 1)
    nStackX(nStackSize) = x
    nStackY(nStackSize) = y
    nStackSize = nStackSize + 1
    nCount = nCount + 1
   End If
  End If
 Loop
End Select
'///check if more than 3 bubbles
If nCount >= 3 Or nColor >= &H100& Then
 '////////check colored bomb
 nStackSize = 0
 For y = 0 To m_nGridHeight
  For x = 0 To m_nGridWidth - 1
   If tmp(x, y) Then
    If (m_bData(x, y) \ &H10&) = &H2& Then
     If nStackSize >= nStackMax Then nStackMax = nStackMax + 128: ReDim Preserve nStackX(nStackMax - 1), nStackY(nStackMax - 1)
     nStackX(nStackSize) = x
     nStackY(nStackSize) = y
     nStackSize = nStackSize + 1
    End If
   End If
  Next x
 Next y
 Do While nStackSize > 0
  nStackSize = nStackSize - 1
  x0 = nStackX(nStackSize)
  y0 = nStackY(nStackSize)
  '///
  x = x0 - 1: y = y0 - 1
  If x >= 0 And y >= 0 Then
   If tmp(x, y) = 0 Then
    tmp(x, y) = 1
    If (m_bData(x, y) \ &H10&) = &H2& Then
     If nStackSize >= nStackMax Then nStackMax = nStackMax + 128: ReDim Preserve nStackX(nStackMax - 1), nStackY(nStackMax - 1)
     nStackX(nStackSize) = x
     nStackY(nStackSize) = y
     nStackSize = nStackSize + 1
    End If
    nCount = nCount + 1
   End If
  End If
  '///
  x = x0 + 1: y = y0 - 1
  If x < m_nGridWidth And y >= 0 Then
   If tmp(x, y) = 0 Then
    tmp(x, y) = 1
    If (m_bData(x, y) \ &H10&) = &H2& Then
     If nStackSize >= nStackMax Then nStackMax = nStackMax + 128: ReDim Preserve nStackX(nStackMax - 1), nStackY(nStackMax - 1)
     nStackX(nStackSize) = x
     nStackY(nStackSize) = y
     nStackSize = nStackSize + 1
    End If
    nCount = nCount + 1
   End If
  End If
  '///
  x = x0 - 2: y = y0
  If x >= 0 Then
   If tmp(x, y) = 0 Then
    tmp(x, y) = 1
    If (m_bData(x, y) \ &H10&) = &H2& Then
     If nStackSize >= nStackMax Then nStackMax = nStackMax + 128: ReDim Preserve nStackX(nStackMax - 1), nStackY(nStackMax - 1)
     nStackX(nStackSize) = x
     nStackY(nStackSize) = y
     nStackSize = nStackSize + 1
    End If
    nCount = nCount + 1
   End If
  End If
  '///
  x = x0 + 2: y = y0
  If x < m_nGridWidth Then
   If tmp(x, y) = 0 Then
    tmp(x, y) = 1
    If (m_bData(x, y) \ &H10&) = &H2& Then
     If nStackSize >= nStackMax Then nStackMax = nStackMax + 128: ReDim Preserve nStackX(nStackMax - 1), nStackY(nStackMax - 1)
     nStackX(nStackSize) = x
     nStackY(nStackSize) = y
     nStackSize = nStackSize + 1
    End If
    nCount = nCount + 1
   End If
  End If
  '///
  x = x0 - 1: y = y0 + 1
  If x >= 0 And y <= m_nGridHeight Then
   If tmp(x, y) = 0 Then
    tmp(x, y) = 1
    If (m_bData(x, y) \ &H10&) = &H2& Then
     If nStackSize >= nStackMax Then nStackMax = nStackMax + 128: ReDim Preserve nStackX(nStackMax - 1), nStackY(nStackMax - 1)
     nStackX(nStackSize) = x
     nStackY(nStackSize) = y
     nStackSize = nStackSize + 1
    End If
    nCount = nCount + 1
   End If
  End If
  '///
  x = x0 + 1: y = y0 + 1
  If x < m_nGridWidth And y <= m_nGridHeight Then
   If tmp(x, y) = 0 Then
    tmp(x, y) = 1
    If (m_bData(x, y) \ &H10&) = &H2& Then
     If nStackSize >= nStackMax Then nStackMax = nStackMax + 128: ReDim Preserve nStackX(nStackMax - 1), nStackY(nStackMax - 1)
     nStackX(nStackSize) = x
     nStackY(nStackSize) = y
     nStackSize = nStackSize + 1
    End If
    nCount = nCount + 1
   End If
  End If
 Loop
 '////////
 For y = 0 To m_nGridHeight
  For x = 0 To m_nGridWidth - 1
   If tmp(x, y) Then
    'Debug.Assert EliminateColorCheck(m_bData(x, y), nColor) Or nColor >= &H100&
    '///add animation
    If x = m_nRaycastDestX And y = m_nRaycastDestY And m_nRaycastHitX >= 0 And m_nRaycastHitY >= 0 Then
     'the ball bounce away
     fx = m_nRaycastA
     If (m_nRaycastCount And 1&) = 0 Then fx = -fx
     fy = m_nRaycastB
     f = m_tRaycast(m_nRaycastCount - 1).dist
     t.x = m_tRaycast(m_nRaycastCount - 1).x + fx * f
     t.y = m_tRaycast(m_nRaycastCount - 1).y + fy * f
     t.vx = m_nRaycastHitX * m_nHorizontalSpace + m_nRadius - t.x
     t.vy = m_nRaycastHitY * m_nVerticalSpace + m_nRadius - t.y
     f = (fx * t.vx + fy * t.vy) / (t.vx * t.vx + t.vy * t.vy) * 2!
     t.vx = (fx - f * t.vx) * 8!
     t.vy = (fy - f * t.vy) * 8!
    Else
     'normal falling animation
     t.x = 6.283185! * Rnd
     t.y = 8! * Rnd
     t.vx = t.y * Cos(t.x)
     t.vy = t.y * Sin(t.x)
     t.x = x * m_nHorizontalSpace + m_nRadius
     t.y = y * m_nVerticalSpace + m_nRadius
    End If
    t.nTargetX = -1
    t.nColor = m_bData(x, y)
    AddFallingBubble t
    '///
    Select Case t.nColor \ &H10&
    Case 1 '+2
     m_nDeletedCount = m_nDeletedCount + 10 'ad-hoc
    Case 2 'bomb
     'add bomb animation
     m_nBombAnimation = 1
     m_bBomb(x, y) = 1
    End Select
    '///delete bubble
    m_bData(x, y) = 0
   End If
  Next x
 Next y
 m_nDeletedCount = m_nDeletedCount + nCount * m_nChainReactionLevel
 m_nScore = m_nScore + nCount * nScoreBase
 '///flood fill again
 nStackSize = 0
 For x = 0 To m_nGridWidth - 1
  If ((x Xor m_nParity) And 1&) = 0 And m_bData(x, 0) <> 0 Then
   nStackX(nStackSize) = x
   nStackY(nStackSize) = 0
   tmp(x, 0) = 2
   nStackSize = nStackSize + 1
  End If
 Next x
 Do While nStackSize > 0
  nStackSize = nStackSize - 1
  x0 = nStackX(nStackSize)
  y0 = nStackY(nStackSize)
  '///
  x = x0 - 1: y = y0 - 1
  If x >= 0 And y >= 0 Then
   If m_bData(x, y) <> 0 And tmp(x, y) < 2 Then
    tmp(x, y) = 2
    If nStackSize >= nStackMax Then nStackMax = nStackMax + 128: ReDim Preserve nStackX(nStackMax - 1), nStackY(nStackMax - 1)
    nStackX(nStackSize) = x
    nStackY(nStackSize) = y
    nStackSize = nStackSize + 1
    nCount = nCount + 1
   End If
  End If
  '///
  x = x0 + 1: y = y0 - 1
  If x < m_nGridWidth And y >= 0 Then
   If m_bData(x, y) <> 0 And tmp(x, y) < 2 Then
    tmp(x, y) = 2
    If nStackSize >= nStackMax Then nStackMax = nStackMax + 128: ReDim Preserve nStackX(nStackMax - 1), nStackY(nStackMax - 1)
    nStackX(nStackSize) = x
    nStackY(nStackSize) = y
    nStackSize = nStackSize + 1
    nCount = nCount + 1
   End If
  End If
  '///
  x = x0 - 2: y = y0
  If x >= 0 Then
   If m_bData(x, y) <> 0 And tmp(x, y) < 2 Then
    tmp(x, y) = 2
    If nStackSize >= nStackMax Then nStackMax = nStackMax + 128: ReDim Preserve nStackX(nStackMax - 1), nStackY(nStackMax - 1)
    nStackX(nStackSize) = x
    nStackY(nStackSize) = y
    nStackSize = nStackSize + 1
    nCount = nCount + 1
   End If
  End If
  '///
  x = x0 + 2: y = y0
  If x < m_nGridWidth Then
   If m_bData(x, y) <> 0 And tmp(x, y) < 2 Then
    tmp(x, y) = 2
    If nStackSize >= nStackMax Then nStackMax = nStackMax + 128: ReDim Preserve nStackX(nStackMax - 1), nStackY(nStackMax - 1)
    nStackX(nStackSize) = x
    nStackY(nStackSize) = y
    nStackSize = nStackSize + 1
    nCount = nCount + 1
   End If
  End If
  '///
  x = x0 - 1: y = y0 + 1
  If x >= 0 And y <= m_nGridHeight Then
   If m_bData(x, y) <> 0 And tmp(x, y) < 2 Then
    tmp(x, y) = 2
    If nStackSize >= nStackMax Then nStackMax = nStackMax + 128: ReDim Preserve nStackX(nStackMax - 1), nStackY(nStackMax - 1)
    nStackX(nStackSize) = x
    nStackY(nStackSize) = y
    nStackSize = nStackSize + 1
    nCount = nCount + 1
   End If
  End If
  '///
  x = x0 + 1: y = y0 + 1
  If x < m_nGridWidth And y <= m_nGridHeight Then
   If m_bData(x, y) <> 0 And tmp(x, y) < 2 Then
    tmp(x, y) = 2
    If nStackSize >= nStackMax Then nStackMax = nStackMax + 128: ReDim Preserve nStackX(nStackMax - 1), nStackY(nStackMax - 1)
    nStackX(nStackSize) = x
    nStackY(nStackSize) = y
    nStackSize = nStackSize + 1
    nCount = nCount + 1
   End If
  End If
 Loop
 '///delete disconnected bubbles
 nCount = 0
 For y = 0 To m_nGridHeight
  For x = 0 To m_nGridWidth - 1
   If tmp(x, y) < 2 Then
    If m_bData(x, y) Then
     '///add animation.
     t.x = 6.283185! * Rnd
     t.y = 8! * Rnd
     t.vx = t.y * Cos(t.x)
     t.vy = t.y * Sin(t.x)
     t.x = x * m_nHorizontalSpace + m_nRadius
     t.y = y * m_nVerticalSpace + m_nRadius
     t.nColor = m_bData(x, y)
     t.nTargetX = -1
     '///now try chain reaction
     If t.nColor < &H10& And Not m_bChainReaction Then
      For k = 1 To 40 'test only
       i = Int(m_nGridWidth * m_objRnd(2).mt_random_real)
       j = Int((m_nGridHeight + 1) * m_objRnd(2).mt_random_real)
       If (i Xor j Xor m_nParity) And 1& Then
        If i = 0 Then i = 1 Else i = i - 1
       End If
       If m_bData(i, j) = 0 Or tmp(i, j) < 2 Then
        tmp2Count = 0
        '///
        ii = i - 1: jj = j - 1
        If ii >= 0 And jj >= 0 Then
         If EliminateColorCheck(m_bData(ii, jj), t.nColor) And tmp(ii, jj) = 2 Then _
         tmp2(tmp2Count * 2) = ii: tmp2(tmp2Count * 2 + 1) = jj: tmp2Count = tmp2Count + 1
        End If
        '///
        ii = i + 1: jj = j - 1
        If ii < m_nGridWidth And jj >= 0 Then
         If EliminateColorCheck(m_bData(ii, jj), t.nColor) And tmp(ii, jj) = 2 Then _
         tmp2(tmp2Count * 2) = ii: tmp2(tmp2Count * 2 + 1) = jj: tmp2Count = tmp2Count + 1
        End If
        '///
        ii = i - 2: jj = j
        If ii >= 0 Then
         If EliminateColorCheck(m_bData(ii, jj), t.nColor) And tmp(ii, jj) = 2 Then _
         tmp2(tmp2Count * 2) = ii: tmp2(tmp2Count * 2 + 1) = jj: tmp2Count = tmp2Count + 1
        End If
        '///
        ii = i + 2: jj = j
        If ii < m_nGridWidth Then
         If EliminateColorCheck(m_bData(ii, jj), t.nColor) And tmp(ii, jj) = 2 Then _
         tmp2(tmp2Count * 2) = ii: tmp2(tmp2Count * 2 + 1) = jj: tmp2Count = tmp2Count + 1
        End If
        '///
        ii = i - 1: jj = j + 1
        If ii >= 0 And jj <= m_nGridHeight Then
         If EliminateColorCheck(m_bData(ii, jj), t.nColor) And tmp(ii, jj) = 2 Then _
         tmp2(tmp2Count * 2) = ii: tmp2(tmp2Count * 2 + 1) = jj: tmp2Count = tmp2Count + 1
        End If
        '///
        ii = i + 1: jj = j + 1
        If ii < m_nGridWidth And jj <= m_nGridHeight Then
         If EliminateColorCheck(m_bData(ii, jj), t.nColor) And tmp(ii, jj) = 2 Then _
         tmp2(tmp2Count * 2) = ii: tmp2(tmp2Count * 2 + 1) = jj: tmp2Count = tmp2Count + 1
        End If
        '///
        If tmp2Count >= 2 Then
         m_bChainReaction = True
        Else
         For kk = 0 To tmp2Count - 1
          ii = tmp2(kk * 2): jj = tmp2(kk * 2 + 1)
          '///
          iii = ii - 1: jjj = jj - 1
          If iii >= 0 And jjj >= 0 Then
           If EliminateColorCheck(m_bData(iii, jjj), t.nColor) And tmp(iii, jjj) = 2 Then _
           m_bChainReaction = True: Exit For
          End If
          '///
          iii = ii + 1: jjj = jj - 1
          If iii < m_nGridWidth And jjj >= 0 Then
           If EliminateColorCheck(m_bData(iii, jjj), t.nColor) And tmp(iii, jjj) = 2 Then _
           m_bChainReaction = True: Exit For
          End If
          '///
          iii = ii - 2: jjj = jj
          If iii >= 0 Then
           If EliminateColorCheck(m_bData(iii, jjj), t.nColor) And tmp(iii, jjj) = 2 Then _
           m_bChainReaction = True: Exit For
          End If
           '///
          iii = ii + 2: jjj = jj
          If iii < m_nGridWidth Then
           If EliminateColorCheck(m_bData(iii, jjj), t.nColor) And tmp(iii, jjj) = 2 Then _
           m_bChainReaction = True: Exit For
          End If
           '///
          iii = ii - 1: jjj = jj + 1
          If iii >= 0 And jjj <= m_nGridHeight Then
           If EliminateColorCheck(m_bData(iii, jjj), t.nColor) And tmp(iii, jjj) = 2 Then _
           m_bChainReaction = True: Exit For
          End If
          '///
          iii = ii + 1: jjj = jj + 1
          If iii < m_nGridWidth And jjj <= m_nGridHeight Then
           If EliminateColorCheck(m_bData(iii, jjj), t.nColor) And tmp(iii, jjj) = 2 Then _
           m_bChainReaction = True: Exit For
          End If
         Next kk
        End If
        '///
        If m_bChainReaction Then
         t.nTargetX = i
         t.nTargetY = j
         Exit For
        End If
        '///
       End If
      Next k
     End If
     '///
     AddFallingBubble t
     '///
     m_bData(x, y) = 0
     nCount = nCount + 1
    End If
   End If
  Next x
 Next y
 m_nDeletedCount = m_nDeletedCount + nCount * m_nChainReactionLevel
 m_nScore = m_nScore + (nCount * m_nChainReactionLevel2 + (nCount * (nCount + 1)) \ 2) * nScoreBase
 m_nChainReactionLevel2 = m_nChainReactionLevel2 + nCount
End If
'///check if we should add obstacles
Debug.Assert m_nObstacleCount = 0
If Not m_bChainReaction Then
 If m_nPendingObstacles > 0 Then
  '///find available position
  i = 0
  For x = 0 To m_nGridWidth - 1
   For y = m_nGridHeight To 0 Step -1
    If ((x Xor y Xor m_nParity) And 1&) = 0 Then
     If m_bData(x, y) Then Exit For
     j = y - 1
     If j >= 0 And x - 1 >= 0 Then
      If m_bData(x - 1, j) Then j = -1
     End If
     If j >= 0 And x + 1 < m_nGridWidth Then
      If m_bData(x + 1, j) Then j = -1
     End If
     If j < 0 Then
      tmp(i, 0) = x
      tmp(i, 1) = y
      i = i + 1
     End If
    End If
   Next y
  Next x
  '///random shuffle
  If i > m_nPendingObstacles Then
   For j = 0 To m_nPendingObstacles - 1
    k = j + Int((i - j) * m_objRnd(2).mt_random_real)
    If k > j Then
     x = tmp(j, 0)
     y = tmp(j, 1)
     tmp(j, 0) = tmp(k, 0)
     tmp(j, 1) = tmp(k, 1)
     tmp(k, 0) = x
     tmp(k, 1) = y
    End If
   Next j
   i = m_nPendingObstacles
  End If
  '///generate obstacles
  Debug.Assert i > 0
  m_nPendingObstacles = m_nPendingObstacles - i
  m_nObstacleCount = i
  For j = 0 To i - 1
   m_tObstacles(j).x = tmp(j, 0)
   m_tObstacles(j).y = tmp(j, 1)
   m_tObstacles(j).nColor = GetRandomMapBubble(2)
  Next j
  If i > 0 Then m_nObstacleTime = 16
 End If
 '///do normal one
 If m_nObstacleCount = 0 Then AddShootCount
End If
'///check if game is over
CheckGameState
'///over
m_nRaycastCount = 0
m_nRaycastDestX = -1
m_nRaycastDestY = -1
m_nFlyingBubble = 0
End Sub

Friend Sub AddShootCount()
'///check if we should add new row
m_nShootCount = m_nShootCount + 1
If m_nShootCount >= m_nMaxShootCount Then
 m_nShootCount = 0
 GenerateNextRow
End If
End Sub

Friend Sub CheckGameState()
Dim x As Long
'///check if game is over
For x = 0 To m_nGridWidth - 1
 If ((x Xor m_nGridHeight Xor m_nParity) And 1&) = 0 Then
  If m_bData(x, m_nGridHeight) Then
   m_bGameOver = True
   Exit For
  End If
 End If
Next x
End Sub

Friend Sub OnTimer()
Dim f As Double
Dim i As Long, j As Long
Dim t As typeFallingBubble
'///check falling bubble.
i = 0
j = 0
Do While i < m_nFallingBubbleCount
 t = m_tFallingBubble(i)
 If t.nColor <> 0 Then
  If t.nTargetTime < 32 Then t.vy = t.vy + 0.5!
  t.x = t.x + t.vx
  t.y = t.y + t.vy
  If t.nTargetX >= 0 And t.nTargetY >= 0 Then
   t.nTargetTime = t.nTargetTime + 1
   If t.nTargetTime >= 64 Then
    EliminateBubble t.nTargetX, t.nTargetY, t.nColor
    t.nColor = 0
   End If
  ElseIf t.y > m_nHeight + m_nRadius Then
   t.nColor = 0
  End If
 End If
 If t.nColor = 0 Then j = j + 1 Else m_tFallingBubble(i - j) = t
 i = i + 1
Loop
m_nFallingBubbleCount = m_nFallingBubbleCount - j
'///check flying bubble
If m_nFlyingBubble <> 0 Then
 m_nFlyingDist = m_nFlyingDist + 12
 f = m_nFlyingDist - m_tRaycast(m_nFlyingIndex).dist
 If f >= 0 Then
  m_nFlyingIndex = m_nFlyingIndex + 1
  m_nFlyingDist = f
  If m_nFlyingIndex >= m_nRaycastCount Then
   EliminateBubble
   m_nFlyingBubble = 0 'in case if previous statement exit-sub'ed
  End If
 End If
End If
'///check shoot time
If m_bGameOver Or m_nFlyingBubble <> 0 Or m_bChainReaction Or m_nObstacleTime > 0 Then
Else
 m_nCurrentTime = m_nCurrentTime - 1
 If m_nCurrentTime <= 0 Then
  OnShoot
  m_nCurrentTime = m_nMaxTime
 End If
End If
'///check obstacles
If m_nObstacleTime > 0 Then
 m_nObstacleTime = m_nObstacleTime - 1
 If m_nObstacleTime <= 0 Then
  For i = 0 To m_nObstacleCount - 1
   m_bData(m_tObstacles(i).x, m_tObstacles(i).y) = m_tObstacles(i).nColor
  Next i
  m_nObstacleCount = 0
  '///
  AddShootCount
  CheckGameState
 End If
End If
'///
End Sub

Friend Sub AddFallingBubble(t As typeFallingBubble)
If m_nFallingBubbleCount >= m_nFallingBubbleMax Then
 m_nFallingBubbleMax = m_nFallingBubbleMax + 64
 ReDim Preserve m_tFallingBubble(m_nFallingBubbleMax - 1)
End If
m_tFallingBubble(m_nFallingBubbleCount) = t
m_nFallingBubbleCount = m_nFallingBubbleCount + 1
End Sub
